from utils.compat import gamethread
from utils.debug import debug_wrap

class StopLoop(Exception): pass

class DelayObject(object):
    """
    Object representing a delay, used to cancel delays.
    """
    def __init__(self, reference, name, seconds, command, args, kwargs):
        self.reference  = reference
        self.name       = name
        self.callback   = command
        self.args       = args or ()
        self.kwargs     = kwargs or {}
        self.seconds    = seconds
    
    def start(self):
        gamethread.delayedname(self.seconds, self.name, self.fake_callback)
        
    def cancel(self):
        """
        Cancel a delay
        """
        gamethread.cancelDelayed(self.name)
        self.reference.delays.remove(self)
        self.reference.manager.delays.remove(self)
        
    def fake_callback(self):
        """
        Remove the delay from the references and manager lists and call the real
        callback.
        """
        self.reference.delays.remove(self)
        self.reference.manager.delays.remove(self)
        self.callback(*self.args, **self.kwargs)
        
        
class LoopObject(object):
    """
    Object representing a loop, used to cancel it.
    """    
    def __init__(self, reference, name, interval, command, args, kwargs):
        self.reference  = reference
        self.name       = name
        self.interval   = interval
        self.callback   = command
        self.args       = args or ()
        self.kwargs     = kwargs or {}
        self.stop       = False
        
    def start(self):
        gamethread.delayedname(self.interval, self.name, self.fake_callback)
        
    def cancel(self):
        gamethread.cancelDelayed(self.name)
        self.reference.delays.remove(self)
        self.reference.manager.delays.remove(self)
        self.stop = True
        
    def fake_callback(self):
        if self.stop:
            return
        try:
            self.callback(*self.args, **self.kwargs)
        except StopLoop:
            self.reference.delays.remove(self)
            self.reference.manager.delays.remove(self)
            return
        if self.stop:
            return
        gamethread.delayedname(self.interval, self.name, self.fake_callback)
    
    
class Reference(object):
    """
    A reference to manage delays attached to an object. This is used to bulk
    delete a bunch of delays at once.
    """
    def __init__(self, manager, ref_id):
        self.free_ids   = [1]
        self.used_ids   = []
        self.delays     = []
        self.ref_id     = ref_id
        self.manager    = manager
        
    def _get_free_id(self):
        """
        Get an internal ID for the delay
        """
        if self.free_ids:
            this_id = min(self.free_ids)
        else:
            this_id = max(self.used_ids) + 1
            self.free_ids.append(this_id)
        self.free_ids.remove(this_id)
        self.used_ids.append(this_id)
        return this_id
        
    @debug_wrap
    def delay(self, seconds, command, args=(), kwargs={}):
        """
        Delay something
        """
        delay_id = self._get_free_id()
        name = '%s_%s_%s' % (self.manager.name, self.ref_id, delay_id)
        delay_object = DelayObject(self, name, seconds, command, args, kwargs)
        self.delays.append(delay_object)
        self.manager.delays.append(delay_object)
        delay_object.start()
        return delay_object
    
    @debug_wrap
    def loop(self, interval, command, args=(), kwargs={}):
        loop_id = self._get_free_id()
        name = '%s_%s_%s' % (self.manager.name, self.ref_id, loop_id)
        loop_object = LoopObject(self, name, interval, command, args, kwargs)
        self.delays.append(loop_object)
        self.manager.delays.append(loop_object)
        loop_object.start()
        return loop_object
    
    def cancel_all(self):
        """
        Cancel all running delays attached to this object
        """
        for delay in self.delays:
            delay.cancel()


class DelayManager(object):
    """
    Manage delays
    """
    def __init__(self, name):
        """
        Name is the namespace for delays used in this manager.
        """
        self.name       = name
        self.references = {}
        self.delays     = []
        
    @debug_wrap
    def get_reference(self, ref):
        """
        Get a Reference object for ref.
        """
        return self._resolve_reference(ref)
        
    @debug_wrap
    def delay(self, ref, seconds, command, args=(), kwargs={}):
        """
        Delay something attached to ref.
        """
        reference = self._resolve_reference(ref)
        delay_object = reference.delay(seconds, command, args, kwargs)
        self.delays.append(delay_object)
        delay_object.start()
        return delay_object
    
    @debug_wrap
    def loop(self, ref, interval, command, args=(), kwargs={}):
        reference = self._resolve_reference(ref)
        loop_object = reference.loop(interval, command, args, kwargs)
        self.delays.append(loop_object)
        loop_object.start()
        return loop_object
        
    @debug_wrap
    def _resolve_reference(self, ref):
        ref_id = id(ref)
        if ref_id in self.references:
            return self.references[ref_id]
        else:
            newref = Reference(self, ref_id)
            self.references[ref_id] = newref
            return newref
        
    def cancel_all(self):
        for delay in self.delays:
            delay.cancel()